package net.hashcoding.cookbook.core.controller;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.hashcoding.cookbook.Function;
import net.hashcoding.cookbook.core.HttpMethod;
import net.hashcoding.cookbook.core.ServiceException;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * Created by Administrator on 2017/6/24.
 */
public abstract class Controller {

    private HashMap<String, ControllerInterface> interfaces;
    private HashMap<String, Set<HttpMethod>> supportedMethods;

    public Controller() {
        interfaces = new HashMap<>();
        supportedMethods = new HashMap<>();
    }

    public void insertInterface(String name, HttpMethod[] methods, ControllerInterface controllerInterface) {
        assert !interfaces.containsKey(name);

        interfaces.put(name, controllerInterface);
        supportedMethods.put(name, new TreeSet<>(Arrays.asList(methods)));
    }

    public void service(HttpMethod method, HttpServletRequest request,
                        HttpServletResponse response) {
        // TODO: get name from request
        String name = "";

        ControllerInterface invocation = getInvokableInterface(name);

        checkIsSupportedMethod(name, method);

        String[] args = getArguments(invocation, request);

        Object result = invocation.invoke(this, (Object[]) args);
        writeResponse(response, result);
    }

    private ControllerInterface getInvokableInterface(String name) {
        ControllerInterface invocation = interfaces.get(name);
        if (invocation == null) {
            throw ServiceException.notFound();
        }
        return invocation;
    }

    private String[] getArguments(ControllerInterface invocation, HttpServletRequest request) {
        int lengthOfParameters = invocation.lengthOfParameters();
        String[] args = new String[lengthOfParameters];

        int idx = 0;
        Map<String, String[]> requestParameters = request.getParameterMap();
        for (ControllerInterface.Parameter parameter : invocation) {
            String parameterName = parameter.getName();
            if (requestParameters.containsKey(parameterName)) {
                String[] values = requestParameters.get(parameterName);
                args[idx++] = StringUtils.join(values, ',');
            } else
                break;
        }
        if (idx != lengthOfParameters) {
            throw ServiceException.requestError();
        }
        return args;
    }

    private void checkIsSupportedMethod(String name, HttpMethod method) {
        Set<HttpMethod> methodSet = supportedMethods.get(name);
        if (methodSet == null || !methodSet.contains(method))
            throw ServiceException.notAccepted();
    }

    private void writeResponse(HttpServletResponse response, Object object) {
        Gson gson = new GsonBuilder().create();
        try {
            gson.toJson(object, response.getWriter());
        } catch (IOException e) {
            e.printStackTrace();
            throw ServiceException.internalError();
        }
    }

    protected HashMap<String, Object> getEnvironment() {
        Transaction transaction = Transaction.getInstance();
        return transaction.getEnvironment();
    }

    public static void transaction(HashMap<String, Object> environment,
                                   Function.Runnable runnable) {
        Transaction transaction = Transaction.getInstance();
        transaction.with(environment, runnable);
    }

    private static class Transaction {
        private static volatile Transaction instance;

        private static Transaction getInstance() {
            if (instance == null) {
                synchronized (Transaction.class) {
                    if (instance == null)
                        instance = new Transaction();
                }
            }
            return instance;
        }

        private HashMap<Long, HashMap<String, Object>> environments;

        private Transaction() {
            environments = new HashMap<>();
        }

        private void with(HashMap<String, Object> environment, Function.Runnable runnable) {
            Long id = Thread.currentThread().getId();
            environments.put(id, environment);

            // ACID property ensure.
            try {
                runnable.call();
            } finally {
                environments.remove(id);
            }
        }

        private HashMap<String, Object> getEnvironment() {
            Long id = Thread.currentThread().getId();
            return environments.get(id);
        }
    }
}
